﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace BorgExt
{
    public class Notifier : RuntimeElement
    {
        private readonly Queue<HandlerInfo> _eventQueue;

        protected internal Notifier()
        {
            _eventQueue = new Queue<HandlerInfo>();
        }

        internal void OnContractAdded(ContractEventArgs args)
        {
            if (TypeStore.ContractAddedEventHandlers == null)
                return;
            
            OnContractEvent(TypeStore.ContractAddedEventHandlers, args);
        }

        internal void OnContractRemoved(ContractEventArgs args)
        {
            if (TypeStore.ContractRemovedEventHandlers == null)
                return;
            
            OnContractEvent(TypeStore.ContractRemovedEventHandlers, args);
        }

        internal void OnPluginAdded(PluginEventArgs args)
        {
            if (TypeStore.PluginAddedEventHandlers == null)
                return;
            
            OnPluginEvent(TypeStore.PluginAddedEventHandlers, args);
        }

        internal void OnPluginRemoved(PluginEventArgs args)
        {
            if (TypeStore.PluginRemovedEventHandlers == null)
                return;
            
            OnPluginEvent(TypeStore.PluginRemovedEventHandlers, args);
        }

        internal bool CanActivate(ExtensionEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanActivateHandlers(args.Extension), args, out log);
        }

        internal bool CanOpen(SlotEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanOpenHandlers(args.Slot), args, out log);
        }

        internal bool CanPlug(CompositionEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanPlugHandlers(args.Slot), args, out log);
        }

        internal bool CanSelect(CompositionEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanSelectHandlers(args.Slot), args, out log);
        }

        internal bool CanDeactivate(ExtensionEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanDeactivateHandlers(args.Extension), args, out log);
        }

        internal bool CanClose(SlotEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanCloseHandlers(args.Slot), args, out log);
        }

        internal bool CanUnplug(CompositionEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanUnplugHandlers(args.Slot), args, out log);
        }

        internal bool CanDeselect(CompositionEventArgs args, out LogEventArgs log)
        {
            return OnCanCompose(GetCanDeselectHandlers(args.Slot), args, out log);
        }

        internal void OnActivating(ExtensionEventArgs args)
        {
            OnComposing(GetActivatingHandlers(args.Extension), args);
        }

        internal void OnOpening(SlotEventArgs args)
        {
            OnComposing(GetOpeningHandlers(args.Slot), args);
        }

        internal void OnPlugging(CompositionEventArgs args)
        {
            OnComposing(GetPluggingHandlers(args.Slot), args);
        }

        internal void OnSelecting(CompositionEventArgs args)
        {
            OnComposing(GetSelectingHandlers(args.Slot), args);
        }

        internal void OnDeactivating(ExtensionEventArgs args)
        {
            OnComposing(GetDeactivatingHandlers(args.Extension), args);
        }

        internal void OnClosing(SlotEventArgs args)
        {
            OnComposing(GetClosingHandlers(args.Slot), args);
        }

        internal void OnUnplugging(CompositionEventArgs args)
        {
            OnComposing(GetUnpluggingHandlers(args.Slot), args);
        }

        internal void OnDeselecting(CompositionEventArgs args)
        {
            OnComposing(GetDeselectingHandlers(args.Slot), args);
        }

        internal void OnCreated(ExtensionEventArgs args)
        {
            OnComposed(GetCreatedHandlers(args.Extension.ExtensionType), args);
        }

        internal void OnActivated(ExtensionEventArgs args)
        {
            OnComposed(GetActivatedHandlers(args.Extension), args);
        }

        internal void OnOpened(SlotEventArgs args)
        {
            OnComposed(GetOpenedHandlers(args.Slot), args);
        }

        internal void OnPlugged(CompositionEventArgs args)
        {
            OnComposed(GetPluggedHandlers(args.Slot), args);
        }

        internal void OnSelected(CompositionEventArgs args)
        {
            OnComposed(GetSelectedHandlers(args.Slot), args);
        }

        internal void OnDestroyed(ExtensionEventArgs args)
        {
            OnComposed(GetDestroyedHandlers(args.Extension.ExtensionType), args);
        }

        internal void OnDeactivated(ExtensionEventArgs args)
        {
            OnComposed(GetDeactivatedHandlers(args.Extension), args);
        }

        internal void OnClosed(SlotEventArgs args)
        {
            OnComposed(GetClosedHandlers(args.Slot), args);
        }

        internal void OnUnplugged(CompositionEventArgs args)
        {
            OnComposed(GetUnpluggedHandlers(args.Slot), args);
        }

        internal void OnDeselected(CompositionEventArgs args)
        {
            OnComposed(GetDeselectedHandlers(args.Slot), args);
        }

        private void OnContractEvent(IEnumerable<ContractEventHandler> handlers, ContractEventArgs args)
        {
            foreach (var contractEventHandler in handlers)
            {
                try
                {
                    contractEventHandler(args);
                }
                catch (Exception ex)
                {
                    Runtime.OnUnhandledException(this, new ExceptionEventArgs(ex));
                }
            }
        }

        private void OnPluginEvent(IEnumerable<PluginEventHandler> handlers, PluginEventArgs args)
        {
            foreach (var pluginEventHandler in handlers)
            {
                try
                {
                    pluginEventHandler(args);
                }
                catch (Exception ex)
                {
                    Runtime.OnUnhandledException(this, new ExceptionEventArgs(ex));
                }
            }
        }

        private bool OnCanCompose<TArgs>(IEnumerable<CanComposeHandler<TArgs>> canHandlers, TArgs args, out LogEventArgs log)
        {
            var allowComposition = Composer.AllowComposition;
            Composer.AllowComposition = false;

            var composable = true;
            log = null;

            foreach (var canComposeHandler in canHandlers)
            {
                try
                {
                    LogEventArgs logArgs;

                    if (canComposeHandler(args, out logArgs))
                        continue;

                    log = logArgs;
                    composable = false;
                    break;
                }
                catch (Exception ex)
                {
                    Runtime.OnUnhandledException(this, new ExceptionEventArgs(ex));
                }
            }
            
            Composer.AllowComposition = allowComposition;
            
            return composable;
        }

        private void OnComposing<TArgs>(IEnumerable<CompositionEventHandler<TArgs>> handlers, TArgs args)
        {
            foreach (var compositionEventHandler in handlers)
            {
                try
                {
                    compositionEventHandler(args);
                }
                catch (Exception ex)
                {
                    Runtime.OnUnhandledException(this, new ExceptionEventArgs(ex));
                }
            }
        }

        private void OnComposed<TArgs>(IEnumerable<CompositionEventHandler<TArgs>> handlers, TArgs args)
        {
            foreach (var handler in handlers)
                _eventQueue.Enqueue(new CompositionHandlerInfo<TArgs>(handler, args));

            while (_eventQueue.Count > 0)
            {
                try
                {
                    _eventQueue.Dequeue().Execute();
                }
                catch (Exception ex)
                {
                    Runtime.OnUnhandledException(this, new ExceptionEventArgs(ex));
                }
            }
        }

        private IEnumerable<CanComposeHandler<ExtensionEventArgs>> GetCanActivateHandlers(Extension extension)
        {
            foreach (var behavior in
                from plug in extension.Plugs
                from slot in plug.SlotsWherePlugged
                where slot.behaviors != null
                from behavior in slot.behaviors
                select behavior)
                yield return behavior.CanActivate;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanActivate;
        }

        private IEnumerable<CanComposeHandler<SlotEventArgs>> GetCanOpenHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.CanOpen;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanOpen;
        }

        private IEnumerable<CanComposeHandler<CompositionEventArgs>> GetCanPlugHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.CanPlug;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanPlug;
        }

        private IEnumerable<CanComposeHandler<CompositionEventArgs>> GetCanSelectHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.CanSelect;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanSelect;
        }

        private IEnumerable<CanComposeHandler<ExtensionEventArgs>> GetCanDeactivateHandlers(Extension extension)
        {
            foreach (var behavior in
                from plug in extension.Plugs
                from slot in plug.SlotsWherePlugged
                where slot.behaviors != null
                from behavior in slot.behaviors
                select behavior)
                yield return behavior.CanDeactivate;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanDeactivate;
        }

        private IEnumerable<CanComposeHandler<SlotEventArgs>> GetCanCloseHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.CanClose;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanClose;
        }

        private IEnumerable<CanComposeHandler<CompositionEventArgs>> GetCanUnplugHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.CanUnplug;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanUnplug;
        }

        private IEnumerable<CanComposeHandler<CompositionEventArgs>> GetCanDeselectHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.CanDeselect;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.CanDeselect;
        }

        private IEnumerable<CompositionEventHandler<ExtensionEventArgs>> GetActivatingHandlers(Extension extension)
        {
            foreach (var behavior in
                from plug in extension.Plugs
                from slot in plug.slotsWherePlugged
                where slot.behaviors != null
                from behavior in slot.behaviors
                select behavior)
                yield return behavior.Activating;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Activating;
        }

        private IEnumerable<CompositionEventHandler<SlotEventArgs>> GetOpeningHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Opening;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Opening;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetPluggingHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Plugging;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Plugging;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetSelectingHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Selecting;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Selecting;
        }

        private IEnumerable<CompositionEventHandler<ExtensionEventArgs>> GetDeactivatingHandlers(Extension extension)
        {
            foreach (var behavior in
                from plug in extension.Plugs
                from slot in plug.SlotsWherePlugged
                where slot.behaviors != null
                from behavior in slot.behaviors
                select behavior)
                yield return behavior.Deactivating;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Deactivating;
        }

        private IEnumerable<CompositionEventHandler<SlotEventArgs>> GetClosingHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Closing;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Closing;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetUnpluggingHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Unplugging;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Unplugging;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetDeselectingHandlers(Slot slot)
        {
            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Deselecting;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Deselecting;
        }

        private IEnumerable<CompositionEventHandler<ExtensionEventArgs>> GetCreatedHandlers(ExtensionType extensionType)
        {
            if (extensionType.CreatedEventHandlers != null)
                foreach (var compositionEventHandler in extensionType.CreatedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.CreatedEventHandlers == null)
                yield break;

            foreach (var compositionEventHandler in Composer.CreatedEventHandlers)
                yield return compositionEventHandler;
        }

        private IEnumerable<CompositionEventHandler<ExtensionEventArgs>> GetActivatedHandlers(Extension extension)
        {
            if (extension.ActivatedEventHandlers != null)
                foreach (var compositionEventHandler in extension.ActivatedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.ActivatedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.ActivatedEventHandlers)
                    yield return compositionEventHandler;

            foreach (var behavior in
                from plug in extension.Plugs
                from slot in plug.SlotsWherePlugged
                where slot.behaviors != null
                from behavior in slot.behaviors
                select behavior)
                yield return behavior.Activated;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Activated;
        }

        private IEnumerable<CompositionEventHandler<SlotEventArgs>> GetOpenedHandlers(Slot slot)
        {
            if (slot.OpenedEventHandlers != null)
                foreach (var compositionEventHandler in slot.OpenedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.OpenedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.OpenedEventHandlers)
                    yield return compositionEventHandler;

            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Opened;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Opened;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetPluggedHandlers(Slot slot)
        {
            if (slot.PluggedEventHandlers != null)
                foreach (var compositionEventHandler in slot.PluggedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.PluggedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.PluggedEventHandlers)
                    yield return compositionEventHandler;

            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Plugged;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Plugged;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetSelectedHandlers(Slot slot)
        {
            if (slot.SelectedEventHandlers != null)
                foreach (var compositionEventHandler in slot.SelectedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.SelectedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.SelectedEventHandlers)
                    yield return compositionEventHandler;

            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Selected;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Selected;
        }

        private IEnumerable<CompositionEventHandler<ExtensionEventArgs>> GetDestroyedHandlers(ExtensionType extensionType)
        {
            if (extensionType.DestroyedEventHandlers != null)
                foreach (var compositionEventHandler in extensionType.DestroyedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.DestroyedEventHandlers == null)
                yield break;

            foreach (var compositionEventHandler in Composer.DestroyedEventHandlers)
                yield return compositionEventHandler;
        }

        private IEnumerable<CompositionEventHandler<ExtensionEventArgs>> GetDeactivatedHandlers(Extension extension)
        {
            if (extension.DeactivatedEventHandlers != null)
                foreach (var compositionEventHandler in extension.DeactivatedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.DeactivatedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.DeactivatedEventHandlers)
                    yield return compositionEventHandler;

            foreach (var behavior in
                from plug in extension.Plugs
                from slot in plug.SlotsWherePlugged
                where slot.behaviors != null
                from behavior in slot.behaviors
                select behavior)
                yield return behavior.Deactivated;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Deactivated;
        }

        private IEnumerable<CompositionEventHandler<SlotEventArgs>> GetClosedHandlers(Slot slot)
        {
            if (slot.ClosedEventHandlers != null)
                foreach (var compositionEventHandler in slot.ClosedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.ClosedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.ClosedEventHandlers)
                    yield return compositionEventHandler;

            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Closed;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Closed;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetUnpluggedHandlers(Slot slot)
        {
            if (slot.UnpluggedEventHandlers != null)
                foreach (var compositionEventHandler in slot.UnpluggedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.UnpluggedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.UnpluggedEventHandlers)
                    yield return compositionEventHandler;

            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Unplugged;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Unplugged;
        }

        private IEnumerable<CompositionEventHandler<CompositionEventArgs>> GetDeselectedHandlers(Slot slot)
        {
            if (slot.DeselectedEventHandlers != null)
                foreach (var compositionEventHandler in slot.DeselectedEventHandlers)
                    yield return compositionEventHandler;

            if (Composer.DeselectedEventHandlers != null)
                foreach (var compositionEventHandler in Composer.DeselectedEventHandlers)
                    yield return compositionEventHandler;

            if (slot.behaviors != null)
                foreach (var behavior in slot.behaviors)
                    yield return behavior.Deselected;

            foreach (var behavior in Composer.Behaviors)
                yield return behavior.Deselected;
        }
    }
}

